﻿/* 
 * 
 * Copyright 2013 Bendsoft. All rights reserved.
 * http://www.bendsoft.com
 * 
 * This code is distributed under the New BSD License (BSD).
 * 
 * @package     Camelot SharePoint Integration Service
 * @codeplex    http://camelottoolkit.codeplex.com
 * @license     New BSD License (BSD)
 * 
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using System.Reflection;
using System.Threading;
using System.ServiceModel;
using Camelot.SharePointIntegration;
using Camelot.SharePointIntegration.IntegrationService;
using Camelot.SharePointIntegration.IntegrationService.Entities;

namespace CamelotSharePointIntegrationService
{

    public partial class IntegrationService : ServiceBase
    {
        private EventLog eventLog;
        private string eventSource = "SharePointIntegrationService";

        public IntegrationService()
        {
            InitializeComponent();

            if (!EventLog.SourceExists(this.eventSource))
                EventLog.CreateEventSource(this.eventSource, "Camelot");

            this.eventLog = new EventLog() { Source = this.eventSource, Log = "Camelot" };
        }

        public override EventLog EventLog
        {
            get
            {
                return this.eventLog;
            }
        }

    }

    public partial class IntegrationService : ServiceBase
    {
        private string pluginPath;
        private string configPath;
        private string logPath;
        private object accessLock = new object();
        private Dictionary<string, ManualResetEvent> resetEvents = new Dictionary<string, ManualResetEvent>();
        private ServiceHost host;

        Configuration configuration;

        protected override void OnStart(string[] args)
        {
            EventLog.WriteEntry("Starting service", EventLogEntryType.Information);

            var servicePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            this.pluginPath = servicePath + @"\plugins";
            this.configPath = servicePath + @"\Tasks.config";
            this.logPath = servicePath + @"\LogFile.txt";

            try
            {
                this.configuration = ServiceConfigurationReader.ReadXml(this.configPath);
            }
            catch (FileNotFoundException)
            {
                var message = String.Format("Service configuration could not be found in location {0}", this.configPath);
                EventLog.WriteEntry(message, EventLogEntryType.Error);
                WriteLog("Error : " + message);
                Stop();
                return;
            }
            catch (Exception ex)
            {
                var message = String.Format("Unable to read service configuration: {0}", ex.Message);
                EventLog.WriteEntry(message, EventLogEntryType.Error);
                WriteLog("Error : " + message);
                Stop();
                return;
            }

            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolveEventHandler;

            StartWaitOrTimerCallback();
        }

        protected override void OnStop()
        {
            EventLog.WriteEntry("Stopping service", EventLogEntryType.Information);
            StopWaitOrTimerCallback();
        }

        protected override void OnPause()
        {
            StopWaitOrTimerCallback();
        }

        protected override void OnContinue()
        {
            StartWaitOrTimerCallback();
        }

        protected override void OnShutdown()
        {
            StopWaitOrTimerCallback();
        }

        protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
        {
            switch (powerStatus) 
            { 
                case PowerBroadcastStatus.Suspend:
                    StopWaitOrTimerCallback();
                    break;
                case PowerBroadcastStatus.ResumeSuspend:
                    StartWaitOrTimerCallback();
                    break;
            }
            return true;
        }

        private void StartWaitOrTimerCallback()
        {
            foreach (var task in this.configuration.Task.Where(m => m.Enabled == true))
            {
                if (!resetEvents.ContainsKey(task.Name))
                    resetEvents.Add(task.Name, new ManualResetEvent(false));

                resetEvents[task.Name].Reset();

                ScheduleTask(task);
            }

            try
            {
                if (System.Configuration.ConfigurationManager.AppSettings["ENABLE_WCF"] != null &&
                    System.Configuration.ConfigurationManager.AppSettings["ENABLE_WCF"].ToLower() == "true")
                {
                    host = new ServiceHost(typeof(Camelot.SharePointIntegration.Wcf.CamelotService));
                    host.Open();
                }
            }
            catch (Exception ex)
            {
                host = null;
                var message = String.Format("Unable to initialize Camelot WCF service: {0}", ex.Message);
                EventLog.WriteEntry(message, EventLogEntryType.Error);
                WriteLog("Error : " + message);
            }
        }

        private void StopWaitOrTimerCallback()
        {
            foreach (var task in this.configuration.Task)
            {
                if (resetEvents.ContainsKey(task.Name) && resetEvents[task.Name] != null)
                    resetEvents[task.Name].Set();
            }

            try
            {
                if (host != null)
                    host.Close();
            }
            finally
            {
                host = null;
            }
        }

        private void PeriodicProcess(object state, bool timeout)
        {
            if (timeout)
            {
                var name = (string)state;
                var task = this.configuration.Task.Where(t => t.Name == name).FirstOrDefault();

                if (task != null)
                {
                    try
                    {
                        var result = ServicePluginManager.ExecuteTask(task);

                        if (!result.Success)
                            WriteLog(String.Format("{0} : {1}", name, result.Message));
                    }
                    catch (Exception ex)
                    {
                        WriteLog(String.Format("{0} : {1}", name, ex.Message));
                    }

                    if (resetEvents.ContainsKey(name) && resetEvents[name] != null)
                        ScheduleTask(task);
                }
            }
        }

        private void WriteLog(string message)
        {
            if (!String.IsNullOrEmpty(this.logPath))
            {
                lock (accessLock)
                    File.AppendAllText(this.logPath, String.Format("{0}: {1}\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), message));
            }
        }

        private void ScheduleTask(Task task)
        {
            if (task.Enabled == false)
                return;

            var startDate = task.DateMask.StartDate < DateTime.Now ? DateTime.Now : task.DateMask.StartDate;
            int waitMilliseconds = 0;

            try
            {
                var nextDateResult = DateMaskScheduler.NextDate(task.DateMask.Value, startDate);

                if (nextDateResult.Success == false)
                {
                    task.Enabled = false;
                    return;
                }

                if (task.DateMask.EndDateSpecified && task.DateMask.EndDate <= nextDateResult.Date)
                {
                    task.Enabled = false;
                    return;
                }

                waitMilliseconds = System.Convert.ToInt32(nextDateResult.Date.Subtract(DateTime.Now).TotalMilliseconds);
            }
            catch (Exception ex)
            {
                WriteLog(String.Format("{0} : {1}", task.Name, ex.Message));
                task.Enabled = false;
                return;
            }

            if (waitMilliseconds < 15000) waitMilliseconds = 15000;
            ThreadPool.RegisterWaitForSingleObject(resetEvents[task.Name], new WaitOrTimerCallback(PeriodicProcess), task.Name, waitMilliseconds, true);
        }

        private Assembly AssemblyResolveEventHandler(object sender, ResolveEventArgs args)
        {
            if (!Directory.Exists(this.pluginPath))
                return null;

            var assemblyFile = args.Name.Split(',')[0] + ".dll";
            var assemblyFilePath = this.pluginPath + @"\" + assemblyFile;

            if (!File.Exists(assemblyFilePath))
                return null;

            try
            {
                return Assembly.LoadFile(assemblyFilePath);
            }
            catch (Exception)
            {
                return null;
            }            
        }
    }

}